import logging
import time
from datetime import datetime, timedelta
from queue import Queue
from threading import Thread

from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.management import BaseCommand

from conf import models
from lib import dingding, sina, utils

User = get_user_model()
log = logging.getLogger("log")

# 临时数据容器
change_rate_cache = sina.ChangeRateCache()
analyze_queue = Queue()
body_queue = Queue()

# 用户数据
user: User
user_live_stocks: list[models.LiveStock] = []
stock_policy: dict[str, models.LiveMonitoringPolicy] = {}
user_dings: list[models.Dingding] = []


def load_user_data(user_id: int):
    """从数据库读取实时监控的股票池，增加或减少股票需要重启服务才能生效"""

    global user_live_stocks, user, user_dings

    user = User.objects.get(pk=user_id)

    user_live_stocks = list(models.LiveStock.objects.filter(user_id=user_id, enable=True))
    user_dings = list(models.Dingding.objects.filter(user_id=user_id, enable=True))

    for stock in user_live_stocks:
        stock_policy.update({
            stock.code: stock.monitoring_policy
        })


def analyze():
    while True:
        measure_list: list[sina.StockMeasure] = analyze_queue.get()
        try:
            for i in measure_list:

                # 指定时间段内股价波动太大，发送通知
                if change_rate_cache.amplitude(i.code) >= stock_policy.get(i.code).amplitude_alert_threshold:
                    body_queue.put(dingding.amplitude_warn_body(i))
                    change_rate_cache.clear(i.code)

        except Exception as e:
            log.error(f"[{user.username}] 分析队列处理异常：{e}", exc_info=True)
        finally:
            analyze_queue.task_done()


def notice():
    while True:
        body = body_queue.get()
        try:
            for ding in user_dings:
                try:
                    dingding.send(body, ding.access_token, ding.secret)
                except Exception as e:
                    log.error(f"[{user.username}] [{ding.name}] 钉钉发消息异常：{e}", exc_info=True)
                    user_dings.remove(ding)
                    continue
        finally:
            body_queue.task_done()


def monitor():
    while True:
        time.sleep(120)
        log.info(change_rate_cache)


class Command(BaseCommand):
    help = "实时行情监控"

    def add_arguments(self, parser):
        parser.add_argument(
            "user_id", nargs="?", help="指定用户"
        )

    def handle(self, *args, **options):

        Thread(target=analyze, daemon=True).start()
        Thread(target=notice, daemon=True).start()
        Thread(target=monitor, daemon=True).start()

        load_user_data(options.get("user_id"))
        log.info(f"用户：{user.username} 启动！")
        if not user_dings:
            raise RuntimeError(f"{user.username} 未配置钉钉通知！")

        batch_10: list[list[models.LiveStock]] = utils.chunk_list(user_live_stocks, 10)

        now = datetime.now()
        today = datetime(year=now.year, month=now.month, day=now.day)
        am_1 = today + timedelta(hours=9, minutes=25)
        am_2 = today + timedelta(hours=9, minutes=30)
        am_3 = today + timedelta(hours=10)
        am_4 = today + timedelta(hours=11, minutes=31)
        pm_1 = today + timedelta(hours=13)
        pm_2 = today + timedelta(hours=15, minutes=1)

        while True:
            now = datetime.now()
            if now <= am_1:
                interval = (am_1 - now).seconds + 1
                log.info(f"not open, wait {interval} seconds")
                time.sleep(interval)
                continue
            # 9:25-9:30 竞价结束，发送开盘价
            elif am_1 < now < am_2:
                body_queue.put(dingding.message_body("Ready!"))
                interval = (am_2 - now).seconds + 1
            # 9:30-10:00 之间波动比较快，监测也要快
            elif am_2 <= now < am_3:
                interval = 10
            #  10:00-11:30  13:00-15:00 正常交易时间
            elif am_3 <= now < am_4 or pm_1 <= now <= pm_2:
                interval = settings.COLLECT_INTERVAL
            # 11:00-13:00 午盘休息
            elif am_4 < now < pm_1:
                interval = (pm_1 - now).seconds + 1
                log.info(f"afternoon close, wait {interval} seconds")
                time.sleep(interval)
                continue
            else:
                log.info("market close!")
                break

            start_time = time.time()
            for stocks in batch_10:
                stock_measures = sina.get_stock_data([i.code for i in stocks])

                for i in stock_measures:
                    change_rate_cache.put(i, stock_policy.get(i.code).cache_length)

                analyze_queue.put(stock_measures)

                if settings.SEND_REAL_PRICE:
                    body_queue.put(dingding.real_price_body(stock_measures, msg_type="text"))
            end_time = time.time()

            sleep_time = max(interval - int(end_time-start_time), 0)
            time.sleep(sleep_time)
